package com.hey.snmp.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.snmp4j.*;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.event.ResponseListener;
import org.snmp4j.mp.MPv1;
import org.snmp4j.mp.MPv2c;
import org.snmp4j.mp.MPv3;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.security.*;
import org.snmp4j.smi.*;
import org.snmp4j.transport.DefaultUdpTransportMapping;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class SnmpUtil {
    private static Logger log = LoggerFactory.getLogger(SnmpUtil.class);
//    public static Snmp snmp = null;
    private static String community = "public";
    private static String ipAddress = "udp:10.0.0.106/";
    public static final String DEFAULT_PROTOCOL = "udp";
    
    /**
     * @description 1.初始化snmp
     * @author YuanFY
     * @date 2017年12月16日 上午10:28:01
     * @version 1.0
     * @throws IOException 
     */
    public static Snmp initSnmp(SNMPTarget target) throws IOException {
        Snmp snmp = null;
        //1、初始化多线程消息转发类
        MessageDispatcher messageDispatcher = new MessageDispatcherImpl();
        //其中要增加三种处理模型。如果snmp初始化使用的是Snmp(TransportMapping<? extends Address> transportMapping) ,就不需要增加
        messageDispatcher.addMessageProcessingModel(new MPv1());
        messageDispatcher.addMessageProcessingModel(new MPv2c());
        //当要支持snmpV3版本时，需要配置user
        if (target.snmpVersion == SnmpConstants.version3) {
            USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0);
            UsmUser user = new UsmUser(new OctetString("SNMPV3"), AuthSHA.ID, new OctetString("authPassword"), PrivAES128.ID, new OctetString("privPassword"));
            usm.addUser(user.getSecurityName(), user);
            messageDispatcher.addMessageProcessingModel(new MPv3(usm));
        }
        //2、创建transportMapping
//            UdpAddress updAddr = (UdpAddress) GenericAddress.parse("udp:10.10.112.177/161");
        TransportMapping transportMapping = new DefaultUdpTransportMapping();
        //3、正式创建snmp
        snmp = new Snmp(messageDispatcher, transportMapping);
        //开启监听
        snmp.listen();
        return snmp;
    }

    public static void closeSnmp(Snmp snmp) throws IOException {
        if (snmp != null){
            snmp.close();
        }
    }

    //2.创建目标地址对象
    public static Target createTarget(SNMPTarget snmpTarget,int targetType){
        if (!(snmpTarget.snmpVersion == SnmpConstants.version3 || snmpTarget.snmpVersion == SnmpConstants.version2c || snmpTarget.snmpVersion == SnmpConstants.version1)) {
            log.error("参数version异常");
            return null;
        }
        Address address = GenericAddress.parse(DEFAULT_PROTOCOL + ":" + snmpTarget.nodeIP + "/" + snmpTarget.port);
        if (snmpTarget.snmpVersion == SnmpConstants.version3) {
            UserTarget target = new UserTarget();
            //IP+Port
            target.setAddress(address);
            //Version
            target.setVersion(snmpTarget.snmpVersion);
            //timeout
            target.setTimeout(snmpTarget.targetSnmpTimeout);
            //Retries
            target.setRetries(snmpTarget.targetSnmpRetry);
            target.setSecurityLevel(snmpTarget.v3SecurityLevel);
            target.setSecurityName(new OctetString(snmpTarget.v3User));
            target.setSecurityModel(snmpTarget.v3PrivacyProtocol);
            return target;
        }else {
            CommunityTarget target = new CommunityTarget();
            //IP+Port
            target.setAddress(address);
            //Version
            target.setVersion(snmpTarget.snmpVersion);
            //Community
            //targetType  1:SET  0：GET
            if(targetType == DefaultConstant.SNMP_COMMUNITY_SET){
                target.setCommunity(new OctetString(snmpTarget.writeCommunity));
            }else {
                target.setCommunity(new OctetString(snmpTarget.readCommunity));
            }
            //timeout
            target.setTimeout(snmpTarget.targetSnmpTimeout);
            //Retries
            target.setRetries(snmpTarget.targetSnmpRetry);
            return target;
        }
    }

    //3.创建消息
    public static PDU createPdu(int version, int type, String oid,Object val){
        PDU pdu = null;
        if (version == SnmpConstants.version3) {
            pdu = new ScopedPDU();
        }else {
            pdu = new PDUv1();
        }
        pdu.setType(type);
        //添加变量oid
        if (type == PDU.SET) {
            pdu.add(getVariableBinding(oid,val));
        }else {
            pdu.add(new VariableBinding(new OID(oid)));
        }
        return pdu;
    }

    public static PDU createPdus(int version, int type, List<String> oids, List vals){
        PDU pdu = null;
        if (version == SnmpConstants.version3) {
            pdu = new ScopedPDU();
        }else {
            pdu = new PDUv1();
        }
        pdu.setType(type);
        //添加多个变量oid
        if (oids != null && oids.size() > 0) {
            for (int i=0;i<oids.size();i++) {
                String oid = oids.get(i);
                if (type == PDU.SET) {
                    pdu.add(getVariableBinding(oid,vals.get(i)));
                }else {
                    pdu.add(new VariableBinding(new OID(oid)));
                }
            }
        }
        return pdu;
    }

    /**
     * 获取表格下面所有值
     * @param snmp
     * @param target
     * @param pdu
     * @param oid
     * @return
     */
    public static List snmpTableWalk(Snmp snmp,Target target,PDU pdu,String oid) {
        List list = new ArrayList();
        try {
            //发送报文，并获取返回结果
            boolean matched = true;
            while (matched) {
                ResponseEvent responseEvent = snmp.send(pdu, target);
                if (responseEvent == null || responseEvent.getResponse() == null) {
                    break;
                }
                PDU response = responseEvent.getResponse();
//                System.out.println("response:"+response.toString());
                String nextOid = null;
                Vector<? extends VariableBinding> variableBindings = response.getVariableBindings();
                for (int i = 0; i < variableBindings.size(); i++) {
                    VariableBinding variableBinding = variableBindings.elementAt(i);
                    Variable variable = variableBinding.getVariable();
                    nextOid = variableBinding.getOid().toString();
                    //如果不是这个节点下的oid则终止遍历，否则会输出很多，直到整个遍历完。
                    if (!nextOid.startsWith(oid)) {
                        matched = false;
                        break;
                    }
                    list.add(variable);
                    System.out.println(variable);
                }
                if (!matched) {
                    break;
                }
                pdu.clear();
                pdu.add(new VariableBinding(new OID(nextOid)));
                System.out.println("返回结果：" + response);
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        return list;
    }

    /**
     * 异步获取表格下面所有值，getNext
     * @param snmp
     * @param target
     * @param pdu
     * @param oid
     * @return
     */
    public static List snmpAsynTableWalk(Snmp snmp,Target target,PDU pdu,String oid) {
        List list = new ArrayList();
        try {
            final CountDownLatch latch = new CountDownLatch(1);
            ResponseListener listener = new ResponseListener() {
                public void onResponse(ResponseEvent event) {
                    ((Snmp) event.getSource()).cancel(event.getRequest(), this);
                    try {
                        PDU response = event.getResponse();
                        if (response == null) {
                            System.out.println("[ERROR]: response is null");
                        } else if (response.getErrorStatus() != 0) {
                            System.out.println("[ERROR]: response status"+ response.getErrorStatus() + " Text:"+ response.getErrorStatusText());
                        } else {
                            System.out.println("Received Walk response value :");
                            VariableBinding vb = response.get(0);
                            boolean finished = checkWalkFinished(new OID(oid),pdu, vb);
                            if (!finished) {
                                System.out.println(vb.getOid() + " = "
                                        + vb.getVariable());
                                pdu.setRequestID(new Integer32(0));
                                pdu.set(0, vb);
                                ((Snmp) event.getSource()).getNext(pdu, target, null, this);
                            } else {
                                System.out.println("SNMP Asyn walk OID value success !");
                                latch.countDown();
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        latch.countDown();
                    }
                }
            };
            snmp.getNext(pdu, target, null, listener);
            System.out.println("pdu 已发送,等到异步处理结果...");
            boolean wait = latch.await(30, TimeUnit.SECONDS);
            System.out.println("latch.await =:" + wait);
        }catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

    private static boolean checkWalkFinished(OID targetOID, PDU pdu,VariableBinding vb) {
        boolean finished = false;
        if (pdu.getErrorStatus() != 0) {
            System.out.println("[true] responsePDU.getErrorStatus() != 0 ");
            System.out.println(pdu.getErrorStatusText());
            finished = true;
        } else if (vb.getOid() == null) {
            System.out.println("[true] vb.getOid() == null");
            finished = true;
        } else if (vb.getOid().size() < targetOID.size()) {
            System.out.println("[true] vb.getOid().size() < targetOID.size()");
            finished = true;
        } else if (targetOID.leftMostCompare(targetOID.size(), vb.getOid()) != 0) {
            System.out.println("[true] targetOID.leftMostCompare() != 0");
            finished = true;
        } else if (Null.isExceptionSyntax(vb.getVariable().getSyntax())) {
            System.out.println("[true] Null.isExceptionSyntax(vb.getVariable().getSyntax())");
            finished = true;
        } else if (vb.getOid().compareTo(targetOID) <= 0) {
            System.out.println("[true] Variable received is not lexicographic successor of requested " + "one:");
            System.out.println(vb.toString() + " <= " + targetOID);
            finished = true;
        }
        return finished;

    }

    /**
     * 根据数据类型得到VariableBinding
     * @param oid 值 OID
     * @param val 值
     * @return
     */
    public static VariableBinding getVariableBinding(String oid,Object val){
        if(val instanceof Integer32){
            //Integer32类型
            return new VariableBinding(new OID(oid),new Integer32(Integer.valueOf(val.toString())));
        }else if(val instanceof OctetString){
            //字符串类型
            return new VariableBinding(new OID(oid),new OctetString(val.toString()));
        }else if(val instanceof Gauge32){
            //Gauge32类型
            return new VariableBinding(new OID(oid),new Gauge32(Long.valueOf(val.toString())));
        }else if(val instanceof IpAddress){
            //ip地址类型
            return new VariableBinding(new OID(oid),new IpAddress(val.toString()));
        }else {
            return null;
        }
    }
}